package com.ruoyi.cms.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.func.VoidFunc1;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.ruoyi.cms.domain.CmsModel;
import com.ruoyi.cms.domain.CmsModelField;
import com.ruoyi.cms.service.ICmsTabService;
import com.ruoyi.cms.utils.CmsDbUtil;
import com.ruoyi.common.exception.BusinessException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CmsTabServiceImpl implements ICmsTabService {

    @Autowired
    CmsDbUtil cmsDbUtil;

    @Override
    public Map<String, Object> select(CmsModel model, long topicId) {
        String table = checkTableName(model);
        List<CmsModelField> fields = model.getCmsModelFieldList();
        Entity res = cmsDbUtil.selectOne(StrUtil.format("select {} from {} where topic_id = {}",
                table, fields.stream().map(CmsModelField::getFieldCode).collect(Collectors.joining(", ")), topicId));
        if (null == res) {
            return new HashMap<>();
        }
        return model.getCmsModelFieldList().stream().collect(Collectors.toMap(CmsModelField::getFieldCode, f -> getDataOutput(f.getFieldCode(), f, res)));
    }

    @Override
    public int delete(CmsModel model, long topicId) {
        String table = checkTableName(model);
        String sql = StrUtil.format("delete from {} where topic_id = {}", table, topicId);
        return cmsDbUtil.delete(sql);
    }

    @Override
    public int update(CmsModel model, long topicId, Map<String, Object> data) {
        String table = checkTableName(model);
        List<CmsModelField> fields = model.getCmsModelFieldList();
        List<String> fcodes = fields.stream().map(CmsModelField::getFieldCode).collect(Collectors.toList());
        Map<String, CmsModelField> fieldsMap = fields.stream().collect(Collectors.toMap(CmsModelField::getFieldCode, f -> f));
        String sql = StrUtil.format("update {} set {} where topic_id = {}",
                table, fcodes.stream().map(c -> c + "=:" + c).collect(Collectors.joining(", ")), topicId);
        Map<String, Object> params = new HashMap<>();
        for (String fcode : fcodes) {
            params.put(fcode, getDataInput(fcode, fieldsMap.get(fcode), data));
        }
        return cmsDbUtil.update(sql, params);
    }

    @Override
    public int insert(CmsModel model, long topicId, Map<String, Object> data) {
        String table = checkTableName(model);
        List<CmsModelField> fields = model.getCmsModelFieldList();
        List<String> fcodes = fields.stream().map(CmsModelField::getFieldCode).collect(Collectors.toList());
        Map<String, CmsModelField> fieldsMap = fields.stream().collect(Collectors.toMap(CmsModelField::getFieldCode, f -> f));
        String sql = StrUtil.format(" insert into {} (topic_id, {}) values ({}, {}) ",
                table, String.join(",", fcodes), topicId, fcodes.stream().map(c -> ":" + c).collect(Collectors.joining(",")) );
        Map<String, Object> params = new HashMap<>();
        for (String fcode : fcodes) {
            params.put(fcode, getDataInput(fcode, fieldsMap.get(fcode), data));
        }
        return cmsDbUtil.insert(sql, params);
    }

    @Override
    @SneakyThrows
    public void createTable(CmsModel model) {
        cmsDbUtil.getDb().tx((VoidFunc1<Db>) db -> {
            db.execute(sqlDropTable(model));
            db.execute(sqlCreateTable(model));
        });
    }

    /**
     * 获取入库数据并校验
     * @param key
     * @param field
     * @param data
     * @return
     */
    private Object getDataInput(String key, CmsModelField field, Map<String, Object> data) {
        assert key.equals(field.getFieldCode());
        Object val = data.getOrDefault(key, null);
        if (val == null || "".equals(String.valueOf(val))) {
            if (field.isReq()) {
                throw new BusinessException(field.getFieldName() + "字段必填");
            }
            return null;
        }
        if (field.isNumber()) {
            try {
                BigDecimal bd = new BigDecimal(String.valueOf(val));
                switch (field.getFieldType()) {
                    case "INT":
                        return bd.intValue();
                    case "BIGINT":
                        return bd.longValue();
                    case "DECIMAL":
                        return bd.setScale(field.getFieldLength(), RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
                    default:
                        throw new RuntimeException("fieldType ERR: " + field.getFieldType());
                }
            } catch (Exception e) {
                throw new BusinessException(field.getFieldName() + "字段无法处理为数值");
            }
        }
        if (field.isVarchar()) {
            String s = String.valueOf(val);
            if (s.length() > field.getFieldLength()) {
                throw new BusinessException(field.getFieldName() + "限制字符最大长度" + field.getFieldLength());
            }
            return s;
        }
        if (field.isText()) {
            return String.valueOf(val);
        }
        if (field.isDate()) {
            if (val instanceof Date) {
                return val;
            }
            try {
                return DateUtil.parse(String.valueOf(val));
            } catch (Exception e) {
                throw new BusinessException(field.getFieldName() + "字段必须是时间类型, 错误的值:" + val);
            }
        }
        throw new RuntimeException(field.getFieldName() + "字段无法处理");
    }

    /**
     * 获取出库数据并格式化
     * @param key
     * @param field
     * @param entity
     * @return
     */
    public Object getDataOutput(String key, CmsModelField field, Entity entity) {
        assert key.equals(field.getFieldCode());
        if (!entity.containsKey(key)) {
            return null;
        }
        if (field.isVarchar() || field.isText()) {
            return entity.getStr(key);
        }
        if ("INT".equals(field.getFieldType())) {
            return entity.getInt(key);
        }
        if (field.isNumber()) {
            return new BigDecimal(entity.getStr(key)).setScale(field.getFieldLength(), RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
        }
        if (field.isDate()) {
            return DateUtil.format(entity.getDate(key), "yyyy-MM-dd HH:mm:ss");
        }
        return entity.getStr(key);
    }
    /**
     * 表字段比较
     * @return
     */
    private static Comparator<CmsModelField> comparator() {
        return (o1, o2) -> {
            int r = o1.getOrd() - o2.getOrd();
            if (r == 0) {
                return StrUtil.compare(o1.getFieldCode(), o2.getFieldCode(), false);
            }
            return r;
        };
    }

    /**
     * 删表语句
     * @param model
     * @return
     */
    private static String sqlDropTable(CmsModel model) {
        String table = checkTableName(model);
        String sql = StrUtil.format("drop table if exists {}", table);
        log.info("[SQL | DROP_TABLE] {}", sql);
        return sql;
    }

    /**
     * 建表语句
     * @param model
     * @return
     */
    public static String sqlCreateTable(CmsModel model) {
        String table = checkTableName(model);
        String cols = model.getCmsModelFieldList().stream()
                .map(CmsTabServiceImpl::sqlCreateColumn).collect(Collectors.joining(", "));
        String sql = StrUtil.format("create table {} ( topic_id bigint primary key, {} )", table, cols);
        log.info("[SQL | CREATE_TABLE] {}", sql);
        return sql;
    }

    /**
     * 创建行语句
     * @param field
     * @return
     */
    private static String sqlCreateColumn(CmsModelField field) {
        String ftype = field.getFieldType();
        if (field.isVarchar()) {
            ftype = ftype + "(" + field.getFieldLength() + ")";
        } else if (field.isDecimal()) {
            ftype = ftype + "(20, " + field.getFieldLength() + ")";
        }
        return field.getFieldCode() + " " +
                ftype + " " +
                (field.isReq() ? "not null" : "default null") + " " +
                "comment '" + field.getFieldName() + "'";
    }

    private static String checkTableName(CmsModel model) {
        String table = model.getTableName();
        assert table.startsWith(CMS_TAB_ + model.getSiteId() + "_");
        return table;
    }
}
